import { join } from 'path'

import emoji from 'remark-emoji'
import images from 'remark-images'
import TerserPlugin from 'terser-webpack-plugin'
import webpack from 'webpack'
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
import { merge } from 'webpack-merge'

import { IS_SCM_BUILD, IN_CI } from '../utils'

import { IgnoreNotFoundExportPlugin } from './ignore-not-found-plugin'
import svgoConfig from './svgo.config.json'

const { packageDir, rootDir } = require('./consts')

const isProduction = () => process.env.NODE_ENV === 'production'

const OptimizeOptionOptions: () => webpack.Configuration['optimization'] = () => ({
  minimize: isProduction(),
  minimizer: [
    new TerserPlugin({
      parallel: true,
      extractComments: true,
      terserOptions: {
        parse: {
          ecma: 2019,
        },
        compress: {
          comparisons: false,
        },
        output: {
          comments: false,
          // https://github.com/facebookincubator/create-react-app/issues/2488
          ascii_only: true,
        },
      },
    }),
  ],
  removeEmptyChunks: true,
  providedExports: true,
  usedExports: true,
  sideEffects: true,
  removeAvailableModules: true,
  runtimeChunk: {
    name: 'runtime',
  },
  splitChunks: {
    chunks: 'all',
    minSize: 1,
    minChunks: 1,
    maxInitialRequests: Number.MAX_SAFE_INTEGER,
    maxAsyncRequests: Number.MAX_SAFE_INTEGER,
    cacheGroups: { default: false, vendors: false },
  },
})

const config: () => webpack.Configuration = () => ({
  context: rootDir,

  output: {
    path: join(rootDir, 'dist'),
    publicPath: isProduction() ? process.env.PUBLIC_PATH ?? '/' : 'http://localhost:8080/',
  },

  mode: isProduction() ? 'production' : 'development',

  devtool: isProduction() ? 'hidden-nosources-source-map' : 'eval-cheap-module-source-map',

  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.gql'],
    alias: {
      '@maiev/platform': join(packageDir, 'platform', 'src'),
      '@maiev/dls': join(packageDir, 'dls', 'src'),
      '@maiev/platform-server': join(packageDir, 'platform-server', 'src'),
      '@maiev/server-common': join(packageDir, 'server-common', 'src'),
      '@maiev/schema': join(packageDir, 'schema', 'src'),
      '@maiev/nestjs-rocketmq': join(packageDir, 'nestjs-rocketmq', 'src'),
      '@perfkit/tracehouse': join(packageDir, 'tracehouse', 'src'),
      '@maiev/shared': join(packageDir, 'shared', 'src'),
      '@maiev/flamechart': join(packageDir, 'flamechart', 'src', 'index.ts'),
      '@maiev/treemap': join(packageDir, 'treemap', 'src', 'index.ts'),
      '@maiev/bundle-analyzer': join(packageDir, 'bundle-analyzer', 'src', 'index.ts'),
      '@maiev/cuckoo': join(packageDir, 'cuckoo', 'src', 'index.ts'),
      '@maiev/source-coverage': join(packageDir, 'source-coverage', 'src', 'index.ts'),
      '@byted/ssa_watermark': join(rootDir, 'node_modules', '@byted', 'ssa_watermark', 'lib', 'watermark.js'),
      '@fluentui/theme': join(rootDir, 'node_modules', '@fluentui', 'theme'),
      '@slardar/react': join(packageDir, 'slardar-react', 'src'),
      tslib: join(rootDir, 'node_modules', 'tslib', 'tslib.es6.js'),
    },
  },

  module: {
    rules: [
      {
        test: /\.m?js?$/,
        resolve: {
          fullySpecified: false,
        },
      },
      {
        include: require.resolve('serialize-javascript'),
        sideEffects: false,
      },
      {
        oneOf: [
          {
            test: /\.tsx?$/,
            use: [
              'thread-loader',
              {
                loader: 'ts-loader',
                options: {
                  transpileOnly: true,
                  happyPackMode: true,
                  configFile: join(process.cwd(), 'tsconfig.json'),
                  experimentalWatchApi: true,
                  getCustomTransformers: join(process.cwd(), 'tools', 'webpack', 'ts-transformers'),
                  compilerOptions: {
                    jsx: isProduction() ? 'react-jsx' : 'react-jsxdev',
                  },
                },
              },
            ],
            exclude: /node_modules/,
          },
          {
            test: /\.mdx?$/,
            use: [
              'thread-loader',
              {
                loader: join(process.cwd(), 'tools', 'webpack', 'loaders', 'mdx-loader.js'),
                options: {
                  remarkPlugins: [images, emoji],
                },
              },
            ],
            exclude: /node_modules/,
          },
          {
            test: /\.svg$/,
            use: [
              'thread-loader',
              {
                loader: '@svgr/webpack',
                options: {
                  icon: true,
                  svgoConfig,
                },
              },
            ],
            exclude: [/node_modules/],
          },
          {
            test: /\.(png|jpg|gif|svg|webp)$/,
            type: 'asset/resource',
          },
          {
            test: /\.(ttf|eot|woff|woff2)$/i,
            type: 'asset/resource',
          },
          {
            test: /\.txt$/,
            loader: 'raw-loader',
          },
        ],
      },
    ],
  },

  plugins: [
    ...(IS_SCM_BUILD || IN_CI ? [] : [new webpack.ProgressPlugin({ percentBy: 'entries' })]),

    ...(process.env.ANA ? [new BundleAnalyzerPlugin()] : []),
    new IgnoreNotFoundExportPlugin(),
  ],
  optimization: OptimizeOptionOptions(),
})

export function runWebpack(
  { entry, project }: { entry: string; project: string },
  mode: 'production' | 'development' = 'development',
  externalConfig: webpack.Configuration = {},
) {
  const mergedConfig = merge(
    config(),
    {
      mode,
      entry,
      output: {
        path: join(rootDir, 'dist', project),
        filename: '[name].[contenthash:8].js',
        chunkFilename: '[name].[contenthash:8].js',
      },
    },
    externalConfig,
  )

  if (mode === 'development') {
    return new Promise(() => {
      webpack(mergedConfig).watch({}, (_, stats) => {
        // eslint-disable-next-line no-console
        console.log(stats?.toString({ colors: true }))
      })
    }).catch((e) => {
      console.error(e)
    })
  }

  return new Promise<void>((resolve, reject) => {
    webpack(mergedConfig).run((err, stats) => {
      if (err) {
        reject(err)
        return
      }
      console.info(stats!.toString({ colors: true }))

      if (process.env.ANA) return
      resolve()
    })
  })
}
